/*
 Xern Entity Relocation Network
 Copyright (C) 2011 Ronie Salgado <roniesalg@gmail.com>

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/

#include "Xern/Exceptions.hpp"
#include "Xern/DataStream.hpp"
#include "MessageTemplate.hpp"

namespace XernMetaverse
{
	using namespace Xern;

    // MessageVariableTemplate implementation
    MessageVariableTemplate::MessageVariableTemplate()
    {
        type = VT_NULL;
        size = 0;
    }

    void MessageVariableTemplate::BuildCache()
    {
        // Do nothing.
    }

    size_t MessageVariableTemplate::GetFixedSize() const
    {
        const size_t TypeSizeMap[] = {
            /*VT_NULL*/          0,
            /*VT_FIXED*/         0,
            /*VT_VARIABLE*/      0,
            /*VT_U8*/            1,
            /*VT_U16*/           2,
            /*VT_U32*/           4,
            /*VT_U64*/           8,
            /*VT_S8*/            1,
            /*VT_S16*/           2,
            /*VT_S32*/           4,
            /*VT_S64*/           8,
            /*VT_F32*/           4,
            /*VT_F64*/           8,
            /*VT_LLVECTOR3*/    12,
            /*VT_LLVECTOR3D*/   24,
            /*VT_LLVECTOR4*/    16,
            /*VT_LLQUATERNION*/ 12,
            /*VT_LLUUID*/       16,
            /*VT_BOOL*/          1,
            /*VT_IPADDR*/        4,
            /*VT_IPPORT*/        2,
            /*VT_U16VEC3*/       6,
            /*VT_U16QUAT*/       8,
            /*VT_S16ARRAY*/      0,
        };

        if(type == VT_FIXED || type == VT_VARIABLE)
            return size;
        return TypeSizeMap[type];
    }

    bool MessageVariableTemplate::HasVariableSize() const
    {
        if(type == VT_VARIABLE)
            return true;
    }

    // MessageBlockTemplate implementation
    MessageBlockTemplate::MessageBlockTemplate()
    {
        quantityType = BQ_SINGLE;
        numrepetitions = 1;
        sizeCache = 0;
        hasVariableSizeCache = false;
    }

    void MessageBlockTemplate::BuildCache()
    {
        // Build each variable cache.
    	//printf("Create Cache of %s(%d)[qt:%d, rp:%d]:\n", name.c_str(),
        // (int)variables.size(), quantityType, numrepetitions);
        for(size_t i = 0; i < variables.size(); i++)
        {
            variablesCache.insert(std::make_pair(variables[i].name, i));

            //printf("%s\n", variables[i].name.c_str());
            variables[i].BuildCache();
        }

        // Only fixed length messages can be cached.
        sizeCache = 0;

        // Check for variable size.
        for(size_t i = 0; i < variables.size(); i++)
        {
            if(variables[i].HasVariableSize())
            {
                hasVariableSizeCache = true;
                break;
            }
        }

        if(!hasVariableSizeCache)
        {
            // Calculate the message size.
            for(size_t i = 0; i < variables.size(); i++)
                sizeCache += variables[i].GetFixedSize();
        }
    }

    size_t MessageBlockTemplate::GetBlockSize() const
    {
        return sizeCache;
    }

    bool MessageBlockTemplate::HasVariableSize() const
    {
        return hasVariableSizeCache;
    }

    // MessageTemplate implementation
    MessageTemplate::MessageTemplate()
    {
        frequency = MF_FIXED;
        messageId = 0;
        isTrusted = false;
        encoding = ME_UNENCODED;
        sizeCache = 0;
        hasVariableSizeCache = false;
    }

    void MessageTemplate::BuildCache()
    {
        for(size_t i = 0; i < blocks.size(); i++)
        {
            blocksCache.insert(std::make_pair(blocks[i]->name, i));
            blocks[i]->BuildCache();
        }

        sizeCache = 0;
        hasVariableSizeCache = false;

        // Check for variable blocks.
        for(size_t i = 0; i < blocks.size(); i++)
        {
            if(blocks[i]->HasVariableSize() ||
               blocks[i]->quantityType == MessageBlockTemplate::BQ_VARIABLE)
            {
                hasVariableSizeCache = true;
                break;
            }
        }

        // Calculate the fixed message length.
        if(!hasVariableSizeCache)
        {
            for(size_t i = 0; i < blocks.size(); i++)
                sizeCache += blocks[i]->GetBlockSize()*blocks[i]->numrepetitions;
        }
    }

    size_t MessageTemplate::GetMessageSize() const
    {
        return sizeCache;
    }

    bool MessageTemplate::HasVariableSize() const
    {
        return hasVariableSizeCache;
    }

    // MessageTemplates implementation
    MessageTemplates::MessageTemplates()
    {
        versionMajor = 0;
        versionMinor = 0;
        crc32 = 0;
    }

    void MessageTemplates::BuildCache()
    {
        for(size_t i = 0; i < templates.size(); i++)
        {
            boost::shared_ptr<MessageTemplate> tmpl = templates[i];
            MessageId tmplId(tmpl->frequency, tmpl->messageId);

            templatesCache.insert(std::make_pair(tmpl->name, i));
            templatesIds.insert(std::make_pair(tmplId, i));

            tmpl->BuildCache();
        }
    }

    // Primitives types.
    typedef std::map<std::string, MessageVariableTemplate::Type> PrimitivesTable;

    static void BuildPrimitiveTypes(PrimitivesTable &table)
    {
#define add(a,b) table.insert(std::make_pair(a,b))
    	add("Null", MessageVariableTemplate::VT_NULL);
        add("U8", MessageVariableTemplate::VT_U8);
        add("U16", MessageVariableTemplate::VT_U16);
        add("U32", MessageVariableTemplate::VT_U32);
        add("U64", MessageVariableTemplate::VT_U64);
        add("S8", MessageVariableTemplate::VT_S8);
        add("S16", MessageVariableTemplate::VT_S16);
        add("S32", MessageVariableTemplate::VT_S32);
        add("S64", MessageVariableTemplate::VT_S64);
        add("F32", MessageVariableTemplate::VT_F32);
        add("F64", MessageVariableTemplate::VT_F64);
        add("LLVector3d", MessageVariableTemplate::VT_LLVECTOR3D);
        add("LLVector3", MessageVariableTemplate::VT_LLVECTOR3);
        add("LLVector4", MessageVariableTemplate::VT_LLVECTOR4);
        add("LLQuaternion", MessageVariableTemplate::VT_LLQUATERNION);
        add("LLUUID", MessageVariableTemplate::VT_LLUUID);
        add("BOOL", MessageVariableTemplate::VT_BOOL);
        add("IPADDR", MessageVariableTemplate::VT_IPADDR);
        add("IPPORT", MessageVariableTemplate::VT_IPPORT);
        add("U16Vec3", MessageVariableTemplate::VT_U16VEC3);
        add("U16Quat", MessageVariableTemplate::VT_U16QUAT);
        add("Fixed", MessageVariableTemplate::VT_FIXED);
        add("Variable", MessageVariableTemplate::VT_VARIABLE);
        add("S16Array", MessageVariableTemplate::VT_S16ARRAY);
#undef add
    };

    // Message template parser.
    class MessageTemplatesParser
    {
    public:
    	MessageTemplatesParser(DataStream &input);
    	~MessageTemplatesParser();

    	void Parse(MessageTemplates &dest);

    private:
    	// Lexer.
    	void Look();
    	void GetToken();

    	// Error handling
    	void Error(const std::string &error);
    	void Match(const std::string &token);

    	// Grammar.
    	void ParseMessageVariable(boost::shared_ptr<MessageBlockTemplate> block);
    	void ParseMessageBlock(boost::shared_ptr<MessageTemplate> message);
    	void ParseMessageTemplate(MessageTemplates &dest);

    	// Input stream.
    	DataStream &input;

    	// Token.
    	std::string token;

    	// Look-ahead.
    	int LA;

    	// Line, column counter.
    	int line, column;

    	// Primitives type.
    	PrimitivesTable primitives;
    };

	inline int ParseInt(const std::string &str)
	{
		return strtol(str.c_str(), NULL, 0);
	}

    MessageTemplatesParser::MessageTemplatesParser(DataStream &input)
		: input(input)
    {
    	line = 1;
    	column = 1;
    	BuildPrimitiveTypes(primitives);
    }

    MessageTemplatesParser::~MessageTemplatesParser()
	{
	}

	void MessageTemplatesParser::Parse(MessageTemplates &dest)
	{
		// Look the first character.
		Look();

		// Get the first token.
		GetToken();

		// Parse first the version.
		Match("version");

		// TODO: Check correctly for a version number.
		if(token.size() != 3 || token[1] != '.')
			Error("Expected template file version.");

		dest.versionMajor = token[0] - '0';
		dest.versionMinor = token[2] - '9';

		// Read the message templates.
		GetToken();
		while(token == "{")
		{
			// Parse the message template.
			ParseMessageTemplate(dest);
		}

		// The last token must be empty.
		Match("");
	}

	// Lexer.
	void MessageTemplatesParser::Look()
	{
		if(!input.ReadLittleTo<u8> (LA))
		{
			LA = 0;
			return;
		}

		// Increase column or line.
		if(LA == '\n')
		{
			line++;
			column = 1;
		}
		else
		{
			column++;
		}
	}

	void MessageTemplatesParser::GetToken()
	{
		char temp[256];
		char *dst = temp;

	skip:
		dst = temp;
		// Skip white spaces.
		while(LA <= ' ' && LA != 0)
			Look();

		// EOF hit.
		if(LA == 0)
		{
			token = "";
			return;
		}

		// Skip comments.
		if(LA == '/')
		{
			*dst++ = '/';
			Look();
			if(LA == '/')
			{
				// Ignore until newline.
				while(LA != '\n')
					Look();

				// Skip again.
				goto skip;
			}
		}

		// Write characters until we hit an space.
		while(LA > ' ')
		{
			// Take special care with comments.
			if(LA == '/')
				break;

			*dst++ = LA;
			Look();
		}

		// Write the token.
		*dst++ = 0;

		token = temp;
	}

	// Error handling
	void MessageTemplatesParser::Error(const std::string &error)
	{
		char buffer[128];
		sprintf(buffer, "%d:%d: %s", line, column, error.c_str());
		throw XernException(buffer);
	}

	void MessageTemplatesParser::Match(const std::string &token)
	{
		if(this->token != token)
			Error("Expected '" + token + "', got '" + token +"");

		// Read the next token.
		GetToken();
	}

	// Grammar.
	void MessageTemplatesParser::ParseMessageVariable(boost::shared_ptr<MessageBlockTemplate> block)
	{
		// Match the entering '{'.
		Match("{");

		// Create the variable.
		MessageVariableTemplate var;

		// Store the variable name.
		var.name = token;

		// Parse the variable type.
		GetToken();

		PrimitivesTable::const_iterator it = primitives.find(token);
		if(it == primitives.end())
			Error("Expected a variables type.");

		var.type = it->second;

		// Read the variable type in special case.
		if(var.type == MessageVariableTemplate::VT_FIXED ||
		   var.type == MessageVariableTemplate::VT_VARIABLE)
		{
			GetToken();
			var.size = ParseInt(token);
		}

		// Match the ending '}'.
		GetToken();
		Match("}");

		// Store the variable.
		block->variables.push_back(var);
	}

	void MessageTemplatesParser::ParseMessageBlock(boost::shared_ptr<MessageTemplate> message)
	{
		// Match the entering '{'.
		Match("{");

		// Create the block.
		boost::shared_ptr<MessageBlockTemplate> block(new MessageBlockTemplate);

		// Store the block name.
		block->name = token;

		// Read the repetition of the block.
		GetToken();
		if(token == "Single")
		{
			block->quantityType = MessageBlockTemplate::BQ_SINGLE;
			block->numrepetitions = 1;
		}
		else if(token == "Multiple")
		{
			block->quantityType = MessageBlockTemplate::BQ_MULTIPLE;

			// Read the number of repetitions.
			GetToken();
			block->numrepetitions = ParseInt(token);
		}
		else if(token == "Variable")
		{
			block->quantityType = MessageBlockTemplate::BQ_VARIABLE;
			block->numrepetitions = 0;
		}
		else
		{
			Error("Expected block quantity.");
		}

		// Read the block variables.
		GetToken();
		while(token == "{")
		{
			ParseMessageVariable(block);
		}

		// Match the ending '}'
		Match("}");

		// Store the block.
		message->blocks.push_back(block);
	}

	void MessageTemplatesParser::ParseMessageTemplate(MessageTemplates &dest)
	{
		// Match the entering '{'
		Match("{");

		// Create the message.
		boost::shared_ptr<MessageTemplate> message(new MessageTemplate);

		// First set the message name.
		message->name = token;

		// Read the frequency
		GetToken();
		if(token == "High")
		{
			message->frequency = MessageTemplate::MF_HIGH;
		}
		else if(token == "Medium")
		{
			message->frequency = MessageTemplate::MF_MEDIUM;
		}
		else if(token == "Low")
		{
			message->frequency = MessageTemplate::MF_LOW;
		}
		else if(token == "Fixed")
		{
			message->frequency = MessageTemplate::MF_FIXED;
		}
		else
		{
			Error("Expected message frequency.");
		}

		// Read the message id.
		GetToken();
		message->messageId = ParseInt(token);

		// Read the trust level.
		GetToken();
		if(token == "Trusted")
		{
			message->isTrusted = true;
		}
		else if(token == "NotTrusted")
		{
			message->isTrusted = false;
		}
		else
		{
			Error("Expected message trust level");
		}

		// Read the message encoding.
		GetToken();
		if(token == "Zerocoded")
		{
			message->encoding = MessageTemplate::ME_ZEROCODED;
		}
		else if(token == "Unencoded")
		{
			message->encoding = MessageTemplate::ME_UNENCODED;
		}
		else
		{
			Error("Expected message encoding");
		}

		// Ignore deprecated attribute.
		GetToken();
		if(token == "UDPDeprecated")
			GetToken();

		// Parse the blocks.
		while(token == "{")
		{
			ParseMessageBlock(message);
		}

		// Match the ending '}'
		Match("}");

		// Store the message template.
		dest.templates.push_back(message);
	}

    bool ParseMessageTemplates(const std::string &data, MessageTemplates &dest)
    {
    	DataStream input(data.size(), (void*)data.c_str());
    	MessageTemplatesParser parser(input);

    	try
    	{
    		parser.Parse(dest);
    	}
    	catch(XernException &e)
    	{
    		fprintf(stderr, "Message template parsing failed:\n %s\n", e.what());
    		return false;
    	}

    	// Build the cache.
    	dest.BuildCache();
    	return true;
    }


    bool ParseMessageTemplates(std::istream &in, MessageTemplates &dest)
    {
        // Read the file into a string.
        std::string str((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
        // Parse the readed string.
        return ParseMessageTemplates(str, dest);
    }

};
